using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using Rensoft.Server.Security;
using System.Data.SqlClient;
using Rensoft.Server.IIS;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;

public class ClientAccount
{
    private int clientId;
    private string guid;
    private string code;
    private WebsiteUser primaryUser;
    private WebsiteUser iusrUser;
    private WebsiteUser iwamUser;

    public int ClientId
    {
        get { return clientId; }
        set { clientId = value; }
    }

    public string Guid
    {
        get { return guid; }
        set { guid = value; }
    }

    public string Code
    {
        get { return code; }
        set { code = value; }
    }

    public WebsiteUser PrimaryUser
    {
        get { return primaryUser; }
        set { primaryUser = value; }
    }

    public string WebsitesDirectory
    {
        get { return Settings.Client.WebsitesDirectory + Path.DirectorySeparatorChar + code; }
    }

    public string ApplicationPoolName
    {
        get { return string.Format(Settings.IIS.ApplicationPoolNameFormat, code); }
    }

    /// <summary>
    /// Parameterless constructor for serialization.
    /// </summary>
    private ClientAccount() { }

    protected ClientAccount(int clientId, string guid, string code)
    {
        this.clientId = clientId;
        this.guid = guid;
        this.code = code;
    }

    protected ClientAccount(int clientId, string guid, string code, WebsiteUser primaryUser)
        : this(clientId, guid, code)
    {
        this.primaryUser = primaryUser;
    }

    internal static bool CheckUserExists(string username)
    {
        WindowsUserManager userManager = new WindowsUserManager(Settings.ServerName);
        return userManager.Exists(username);
    }

    internal static ClientAccount Create(
        string guid, 
        string code, 
        string fullName, 
        string username, 
        string password)
    {
        int clientId;
        using (SqlConnection connection = new SqlConnection(Settings.ConnectionStrings.HostingDatabase))
        {
            connection.Open();

            SqlCommand command = new SqlCommand("ClientCreate", connection);
            command.CommandType = CommandType.StoredProcedure;

            command.Parameters.AddWithValue("guid", guid);
            command.Parameters.AddWithValue("code", code);
            command.Parameters.Add("clientId", SqlDbType.Int).Direction = ParameterDirection.Output;

            command.ExecuteNonQuery();
            clientId = (int)command.Parameters["clientId"].Value;

            connection.Close();
        }

        ClientAccount client = new ClientAccount(clientId, guid, code);
        
        VirtualServerManager vsManager = new VirtualServerManager(Settings.ServerName);

        client.primaryUser = new WebsiteUser(
            clientId, username, password, WebsiteUserType.System, fullName, true);

        client.iusrUser = new WebsiteUser(
            clientId, Settings.Client.AnonymousUserPrefix + username,
            vsManager.IusrPassword, WebsiteUserType.Anonymous, string.Empty, false);

        client.iwamUser = new WebsiteUser(
            clientId, Settings.Client.WorkerProcessUserPrefix + username,
            vsManager.IwamPassword, WebsiteUserType.WorkerProcess, string.Empty, false);

        client.CreateEnvironment();

        // Create in database after SIDs set.
        client.primaryUser.Create();
        client.iusrUser.Create();
        client.iwamUser.Create();

        return client;
    }

    protected void CreateEnvironment()
    {
        #region Windows Users
        WindowsUser primarySystemUser = new WindowsUser(
            primaryUser.Username, primaryUser.Password, primaryUser.FullName,
            string.Format(Settings.Client.SystemUserDescriptionFormat, primaryUser.FullName),
            Settings.Client.DefaultUserFlags);

        WindowsUser iusrSystemUser = new WindowsUser(
            iusrUser.Username, iusrUser.Password,
            Settings.Client.AnonymousUserFullName,
            Settings.Client.AnonymousUserDescription,
            Settings.Client.DefaultUserFlags);

        WindowsUser iwamSystemUser = new WindowsUser(
            iwamUser.Username, iwamUser.Password,
            Settings.Client.WorkerProcessUserFullName,
            Settings.Client.WorkerProcessUserDescription,
            Settings.Client.DefaultUserFlags,
            new WindowsUserGroup("IIS_WPG"));

        WindowsUserManager userManager = new WindowsUserManager(Settings.ServerName);
        primaryUser.Sid = userManager.Create(primarySystemUser).ToString();
        iusrUser.Sid = userManager.Create(iusrSystemUser).ToString();
        iwamUser.Sid = userManager.Create(iwamSystemUser).ToString();
        #endregion

        #region Application Pool
        ApplicationPool applicationPool = new ApplicationPool(ApplicationPoolName, iwamSystemUser);
        ApplicationPoolManager appPoolManager = new ApplicationPoolManager(Settings.ServerName);
        appPoolManager.Create(applicationPool);
        #endregion

        #region Websites Directory
        // Rights used for all users.
        FileSystemRights rights =
            FileSystemRights.Read |
            FileSystemRights.Synchronize;

        // Inheritance for all users.
        InheritanceFlags inheritance =
            InheritanceFlags.ContainerInherit |
            InheritanceFlags.ObjectInherit;

        Directory.CreateDirectory(WebsitesDirectory);
        DirectorySecurity security = Directory.GetAccessControl(WebsitesDirectory);
        
        // Client system user.
        security.AddAccessRule(new FileSystemAccessRule(
            new SecurityIdentifier(primaryUser.Sid),
            rights, inheritance,
            PropagationFlags.None,
            AccessControlType.Allow));

        // Anonymous user.
        security.AddAccessRule(new FileSystemAccessRule(
            new SecurityIdentifier(iusrUser.Sid),
            rights, inheritance,
            PropagationFlags.None,
            AccessControlType.Allow));

        // Worker process user.
        security.AddAccessRule(new FileSystemAccessRule(
            new SecurityIdentifier(iwamUser.Sid),
            rights, inheritance,
            PropagationFlags.None,
            AccessControlType.Allow));

        Directory.SetAccessControl(WebsitesDirectory, security);
        #endregion
    }

    internal static ClientAccount Fetch(string guid)
    {
        using (SqlConnection connection = new SqlConnection(Settings.ConnectionStrings.HostingDatabase))
        {
            SqlCommand command = new SqlCommand("ClientFetchByGuid", connection);
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.AddWithValue("guid", guid);

            connection.Open();

            SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow);
            if (!reader.Read())
            {
                throw new InvalidOperationException(
                    "Client not found with GUID '" + guid + "'");
            }

            WebsiteUser primaryUser = new WebsiteUser(
                (int)reader["UserId"],
                (int)reader["ClientId"],
                (string)reader["Username"],
                (string)reader["Password"],
                (WebsiteUserType)reader["TypeFlag"],
                (string)reader["FullName"],
                true, // Already know it's a primary user.
                (string)reader["Sid"]);

            ClientAccount client = new ClientAccount(
                (int)reader["ClientId"],
                (string)reader["Guid"],
                (string)reader["Code"],
                primaryUser);

            connection.Close();

            return client;
        }
    }

    internal static bool CheckGuidExists(string guid)
    {
        bool clientExists = false;
        using (SqlConnection connection = new SqlConnection(Settings.ConnectionStrings.HostingDatabase))
        {
            SqlCommand command = new SqlCommand("ClientFetchByGuid", connection);
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.AddWithValue("guid", guid);

            connection.Open();

            SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow);
            clientExists = reader.Read();

            connection.Close();
        }
        return clientExists;
    }

    internal WebsiteUser FetchSingleUser(WebsiteUserType type)
    {
        foreach (WebsiteUser user in WebsiteUser.Fetch(this))
        {
            if (user.Type == type)
            {
                return user;
            }
        }

        throw new InvalidOperationException(
            "Cannot find any users where type is '" + type.ToString() + "'");
    }

    internal static void Remove(string guid)
    {
        throw new NotImplementedException(
            "Removal of hosting clients has not yet been implemented.");
    }
}